# -*- coding: utf-8 -*-
__all__ = [
    # public symbols
    "CacheInfo",
    "CacheStorageInfo",
    "TileCacheInfo", "TileImageInfo",
    "LOD", "ImageFormat",
    "MapCacheStorageFormat",
    "explodeBundleWithBundlx",
    "explodeBundle",
    "explodeBundleWithBundlxFromBytes",
    "explodeTileInBundleWithBundlxFromBytesV1",
    "judgeSubBundle"
]

from xml.etree import ElementTree
from enum import Enum
import sys
import math
import os
from struct import *
from src.formats.ESRIBundle.Geometry import *
from io import BytesIO

class CacheInfo(object):
    # 构造函数-----------------------------------
    def __init__(self):
        self.__tileCacheInfo = TileCacheInfo()
        self.__tileImageInfo = TileImageInfo()
        self.__cacheStorageInfo = CacheStorageInfo()
        self.__fullExtent = Envelope()

    # 公共属性------------------------------------
    @property
    def tileCacheInfo(self):
        return self.__tileCacheInfo

    @tileCacheInfo.setter
    def tileCacheInfo(self, value):
        self.__tileCacheInfo = value

    @property
    def tileImageInfo(self):
        return self.__tileImageInfo

    @tileImageInfo.setter
    def tileImageInfo(self, value):
        self.__tileImageInfo = value

    @property
    def cacheStorageInfo(self):
        return self.__cacheStorageInfo

    @cacheStorageInfo.setter
    def setCacheStorageInfo(self, value):
        self.__cacheStorageInfo = value

    @property
    def fullExtent(self):
        return self.__fullExtent

    @fullExtent.setter
    def fullExtent(self, value):
        self.__fullExtent = value

    # 静态方法----------------------------------------------
    @staticmethod
    def ReadFromXMLFile(confFilename, cdiFileName=None):
        conftree = ElementTree.parse(confFilename)
        if cdiFileName is None:
            return CacheInfo.__ReadFromXML(conftree)
        cditree = ElementTree.parse(cdiFileName)
        return CacheInfo.__ReadFromXML(conftree, cditree)

    @staticmethod
    def ReadFromXMLString(confXml, cdiXml=None):
        # ymy
        isRoot = True
        conftree = ElementTree.fromstring(confXml)
        if cdiXml is None:
            return CacheInfo.__ReadFromXML(conftree)
        cditree = ElementTree.fromstring(cdiXml)
        return CacheInfo.__ReadFromXML(conftree, cditree, True)

    @staticmethod
    def __ReadFromXML(conftree, cditree=None, isRoot=False):
        target = CacheInfo()
        if isRoot is False:
            root = conftree.getroot()
        else:
            root = conftree
        # 解析 Tile Cache Info
        node = root.find("TileCacheInfo")
        if node is not None:
            strWKT = node.find('SpatialReference').find('WKT').text
            if not str.isspace(strWKT):
                target.tileCacheInfo.spatialReference.WKT = strWKT
            strWKID = node.find('SpatialReference').find('WKID').text
            if str.isdigit(strWKID):
                target.tileCacheInfo.spatialReference.WKID = int(strWKID)

            strX = node.find('TileOrigin').find('X').text
            target.tileCacheInfo.tileOrigin.x = float(strX)
            strY = node.find('TileOrigin').find('Y').text
            target.tileCacheInfo.tileOrigin.y = float(strY)

            strCols = node.find('TileCols').text
            target.tileCacheInfo.tileCols = int(strCols)
            strRows = node.find('TileRows').text
            target.tileCacheInfo.tileRows = int(strRows)
            strDpi = node.find('DPI').text
            target.tileCacheInfo.dpi = int(strDpi)

            lst_node = node.find('LODInfos').findall('LODInfo')
            for subnode in lst_node:
                lod = LOD()
                lod.level = int(subnode.find('LevelID').text)
                lod.Scale = float(subnode.find('Scale').text)
                lod.resolution = float(subnode.find('Resolution').text)
                target.tileCacheInfo.lodInfos.append(lod)

        # 解析 Tile Image Info
        node = root.find("TileImageInfo")
        if node is not None:
            strTileFormat = node.find('CacheTileFormat').text
            target.tileImageInfo.cacheTileFormat = ImageFormat.parse("", strTileFormat)
            strQuality = node.find('CompressionQuality').text
            target.tileImageInfo.compressionQuality = strQuality
            strAntialiassing = node.find('Antialiasing').text
            target.tileImageInfo.antialiasing = bool(strAntialiassing)

        # 解析 Cache Storage Info
        node = root.find("CacheStorageInfo")
        if node is not None:
            strStorageFormat = node.find("StorageFormat").text
            target.cacheStorageInfo.storageFormat = MapCacheStorageFormat.parse("", strStorageFormat)
            # print(target.cacheStorageInfo.storageFormat)
            strpacketsize = node.find('PacketSize').text
            target.cacheStorageInfo.packetSize = int(strpacketsize)
            # print(target.cacheStorageInfo.packetSize)

        if cditree is not None:
            if isRoot is False:
                root = cditree.getroot()
            else:
                root = cditree

            strXmin = root.find('XMin').text
            strYmin = root.find('YMin').text
            strXmax = root.find('XMax').text
            strYmax = root.find('YMax').text
            target.fullExtent.xmax = float(strXmax)
            target.fullExtent.xmin = float(strXmin)
            target.fullExtent.ymax = float(strYmax)
            target.fullExtent.ymin = float(strYmin)

        return target


class CacheStorageInfo(object):
    # 构造函数
    def __init__(self):
        self.storageFormat = MapCacheStorageFormat.esriMapCacheStorageModeCompact
        self.packetSize = 0


class TileCacheInfo(object):
    # 构造函数
    def __init__(self):
        self.spatialReference = SpatialReference()
        self.tileOrigin = MapPoint()
        self.tileCols = 0
        self.tileRows = 0
        self.dpi = 0
        self.lodInfos = []


class TileImageInfo(object):
    # 构造函数
    def __init__(self):
        self.cacheTileFormat = None
        self.compressionQuality = None
        self.antialiasing = None


class LOD(object):
    def __init__(self):
        self.level = 0
        self.resolution = 0
        self.Scale = 0


class ImageFormat(Enum):
    PNG32 = 1
    PNG24 = 2
    PNG8 = 3
    PNG = 4
    JPG = 5
    JPEG = 6

    def parse(self,strformat):
        switcher = {
            'PNG32': ImageFormat.PNG32,
            'PNG24': ImageFormat.PNG24,
            'PNG8': ImageFormat.PNG8,
            'PNG': ImageFormat.PNG,
            'JPG': ImageFormat.JPG,
            'JPEG': ImageFormat.JPEG
        }
        return switcher.get(strformat)


class MapCacheStorageFormat(Enum):
    esriMapCacheStorageModeExploded = 1
    esriMapCacheStorageModeCompact = 2

    def parse(self,strformat):
        switcher = {
            'esriMapCacheStorageModeExploded': MapCacheStorageFormat.esriMapCacheStorageModeExploded,
            'esriMapCacheStorageModeCompact': MapCacheStorageFormat.esriMapCacheStorageModeCompact
        }
        return switcher.get(strformat)
def explodeBundleWithBundlx(bundleFilename, bundlxFilename, packetSize, callback=None):
    INDEX_RECORD_SIZE = 5
    # 获取Tile索引的偏移量
    def __getBundlxOffset(row, column, packetSize):
        tileStartRow = (row // packetSize) * packetSize
        tileStartCol = (column // packetSize) * packetSize
        recordNumber = (
            ((packetSize * (column - tileStartCol)) + (row - tileStartRow)))
        if recordNumber < 0:
            raise ValueError('无效的行列号')
        offset = 16 + (recordNumber * INDEX_RECORD_SIZE)
        return offset

    # 函数主体
    bundleName = os.path.basename(bundleFilename)
    bundleName = os.path.splitext(bundleName)[0]
    bundleName=bundleName.split('R')[1]
    bundleName=bundleName.split('C')

    rowIndex = int(bundleName[0], 16)
    colIndex = int(bundleName[1], 16)

    try:
        bundle = open(bundleFilename, 'rb')
        bundlx = open(bundlxFilename, 'rb')

        row = 0
        while row < packetSize:
            column = 0
            while column < packetSize:
                bundlxOffset = __getBundlxOffset(row, column, packetSize)
                bundlx.seek(bundlxOffset, 0)
                idxData = bundlx.read(INDEX_RECORD_SIZE)
                bundleOffset = (((idxData[4] & 0xFF) << 32) |
                                ((idxData[3] & 0xFF) << 24) |
                                ((idxData[2] & 0xFF) << 16) |
                                ((idxData[1] & 0xFF) << 8) |
                                ((idxData[0] & 0xFF)))
                bundle.seek(bundleOffset, 0)
                idxData = bundle.read(4)
                dataLength = (((idxData[3] & 0xFF) << 24) |
                              ((idxData[2] & 0xFF) << 16) |
                              ((idxData[1] & 0xFF) << 8) |
                              ((idxData[0] & 0xFF)))

                # 读取数据，回调保存数据
                if dataLength > 0:
                    imgData = bundle.read(dataLength)
                    if callback is not None:
                        callback(rowIndex + row, colIndex + column, imgData)

                column = column + 1
            row = row + 1
    finally:
        if bundle:
            bundle.close()
        if bundlx:
            bundlx.close()


# ymy 从字节流boundle中读取数据
def explodeBundleWithBundlxFromBytes(bundleFilename, bundlxFilename, bundleFile, bundlxFile, packetSize, callback=None):
    INDEX_RECORD_SIZE = 5
    # 获取Tile索引的偏移量
    def __getBundlxOffset(row, column, packetSize):
        tileStartRow = (row // packetSize) * packetSize
        tileStartCol = (column // packetSize) * packetSize
        recordNumber = (
            ((packetSize * (column - tileStartCol)) + (row - tileStartRow)))
        if recordNumber < 0:
            raise ValueError('无效的行列号')
        offset = 16 + (recordNumber * INDEX_RECORD_SIZE)
        return offset

    # 函数主体
    bundleName = os.path.basename(bundleFilename)
    bundleName = os.path.splitext(bundleName)[0]
    bundleName = bundleName.split('R')[1]
    bundleName = bundleName.split('C')

    rowIndex = int(bundleName[0], 16)
    colIndex = int(bundleName[1], 16)
    try:
        bundle = BytesIO(bundleFile)
        bundlx = BytesIO(bundlxFile)

        row = 0
        while row < packetSize:
            column = 0
            while column < packetSize:
                bundlxOffset = __getBundlxOffset(row, column, packetSize)
                bundlx.seek(bundlxOffset, 0)
                idxData = bundlx.read(INDEX_RECORD_SIZE)
                bundleOffset = (((idxData[4] & 0xFF) << 32) |
                                ((idxData[3] & 0xFF) << 24) |
                                ((idxData[2] & 0xFF) << 16) |
                                ((idxData[1] & 0xFF) << 8) |
                                ((idxData[0] & 0xFF)))
                bundle.seek(bundleOffset, 0)
                idxData = bundle.read(4)
                dataLength = (((idxData[3] & 0xFF) << 24) |
                              ((idxData[2] & 0xFF) << 16) |
                              ((idxData[1] & 0xFF) << 8) |
                              ((idxData[0] & 0xFF)))

                # 读取数据，回调保存数据
                if dataLength > 0:
                    imgData = bundle.read(dataLength)
                    if callback is not None:
                        callback(rowIndex + row, colIndex + column, imgData)

                column = column + 1
            row = row + 1
    except Exception as e:
        print(e)
        print('error')
    finally:
        print('finally')

# V1版本从字节流boundle中读取指定数据
def explodeTileInBundleWithBundlxFromBytesV1(r, c, bundleName, bundleFile, bundlxFile, packetSize, callback=None):
    INDEX_RECORD_SIZE = 5
    # 获取Tile索引的偏移量

    # 求切片的序数
    def __getBundlxOffset(row, column, packetSize): # packetSize = 128
        tileStartRow = (row // packetSize) * packetSize
        tileStartCol = (column // packetSize) * packetSize
        recordNumber = (((packetSize *(column - tileStartCol)) +   (row - tileStartRow)))
        if recordNumber < 0:
            raise ValueError('无效的行列号')
        offset = 16 + (recordNumber * INDEX_RECORD_SIZE)
        return offset
    # 函数主体
    bundleName = bundleName.split('R')[1]
    bundleName = bundleName.split('C')

    rowIndex = int(bundleName[0], 16)
    colIndex = int(bundleName[1], 16)
    try:
        bundle = BytesIO(bundleFile)
        bundlx = BytesIO(bundlxFile)
        row = r % packetSize
        if row < packetSize:
            column = c % packetSize
            if column < packetSize:
                bundlxOffset = __getBundlxOffset(row, column, packetSize)
                bundlx.seek(bundlxOffset, 0)
                idxData = bundlx.read(INDEX_RECORD_SIZE)
                bundleOffset = (((idxData[4] & 0xFF) << 32)
                                | ((idxData[3] & 0xFF) << 24)
                                | ((idxData[2] & 0xFF) << 16)
                                | ((idxData[1] & 0xFF) << 8)
                                | ((idxData[0] & 0xFF)))

                bundle.seek(bundleOffset, 0)
                idxData = bundle.read(4)
                dataLength = ((idxData[3] & 0xFF) << 24) | ((idxData[2] & 0xFF) << 16) | ((idxData[1] & 0xFF) << 8) | ((idxData[0] & 0xFF))

                # 读取数据，回调保存数据
                if dataLength > 0:
                    imgData = bundle.read(dataLength)
                    return imgData
                # return bundle.read(dataLength)
                    # if callback is not None:
                    #     callback(rowIndex + row, colIndex + column, imgData)

    except Exception as e:
        print(e)
        print('error')
    finally:
        print('finally')

# V2版本从字节流boundle中读取指定数据
def explodeTileInBundleWithBundlxFromBytesV2(r, c, bundleName, bundleFile, bundlxFile, packetSize, callback=None):
    INDEX_RECORD_SIZE = 8
    # 获取Tile索引的偏移量

    # 求切片的序数
    def __getBundlxOffset(row, column, packetSize): # packetSize = 128
        tileStartRow = (row // packetSize) * packetSize
        tileStartCol = (column // packetSize) * packetSize
        recordNumber = ((((column - tileStartCol)) + packetSize *  (row - tileStartRow)))
        if recordNumber < 0:
            raise ValueError('无效的行列号')
        offset = 64 + (recordNumber * INDEX_RECORD_SIZE)
        return offset
    # 函数主体
    # bundleName = bundleName.split('R')[1]
    # bundleName = bundleName.split('C')

    # rowIndex = int(bundleName[0], 16)
    # colIndex = int(bundleName[1], 16)
    try:
        bundle =BytesIO(bundleFile)
        bundlx = BytesIO(bundlxFile)
        row = r % packetSize
        column = c % packetSize
        if row < packetSize:
            column = c % packetSize
            if column < packetSize:
                bundlxOffset = __getBundlxOffset(row, column, packetSize)
                bundlx.seek(bundlxOffset, 0)
                idxData = bundlx.read(INDEX_RECORD_SIZE)
                bundleOffset = (((idxData[3] & 0xFF) << 24)
                                | ((idxData[2] & 0xFF) << 16)
                                | ((idxData[1] & 0xFF) << 8)
                                | ((idxData[0] & 0xFF)))

                bundle.seek(bundleOffset, 0)
                idxData = bundle.read(4)
                dataLength = (((idxData[3] & 0xFF) << 24)
                              | ((idxData[2] & 0xFF) << 16)
                              | ((idxData[1] & 0xFF) << 8)
                              | ((idxData[0] & 0xFF)))

                # 读取数据，回调保存数据
                if dataLength > 0:
                    imgData = bundle.read(dataLength)
                    return imgData
                # return bundle.read(dataLength)
                    # if callback is not None:
                    #     callback(rowIndex + row, colIndex + column, imgData)
    except Exception as e:
        print(e)
        print('error')
    finally:
        print('finally')

# V2版本
def explodeBundle(bundleFilename, packetSize, callback=None):
    INDEX_RECORD_SIZE = 8

    # 获取Tile索引的偏移量
    def __getBundlxOffset(row, column, packetSize):
        tileStartRow = (row // packetSize) * packetSize
        tileStartCol = (column // packetSize) * packetSize
        recordNumber = (((packetSize * (row - tileStartCol)) + (column - tileStartRow)))
        if recordNumber < 0:
            raise ValueError('无效的行列号')
        offset = 64 + (recordNumber * INDEX_RECORD_SIZE)
        return offset

    # 函数主体
    bundleName = os.path.basename(bundleFilename)
    bundleName = os.path.splitext(bundleName)[0]
    bundleName=bundleName.split('R')[1]
    bundleName=bundleName.split('C')

    rowIndex = int(bundleName[0], 16)
    colIndex = int(bundleName[1], 16)

    try:
        bundle = open(bundleFilename, 'rb')
        row = 0
        while row < packetSize:
            column = 0
            while column < packetSize:
                bundlxOffset = __getBundlxOffset(row, column, packetSize)
                bundle.seek(bundlxOffset, 0)
                idxData = bundle.read(INDEX_RECORD_SIZE)
                bundleOffset = (((idxData[4] & 0xFF) << 32) |
                                ((idxData[3] & 0xFF) << 24) |
                                ((idxData[2] & 0xFF) << 16) |
                                ((idxData[1] & 0xFF) << 8) |
                                ((idxData[0] & 0xFF)))
                print(bundleOffset)
                if (bundleOffset - 4) > 0:
                    bundle.seek(bundleOffset - 4, 0)
                    idxData = bundle.read(4)
                    dataLength = (((idxData[3] & 0xFF) << 24) |
                                  ((idxData[2] & 0xFF) << 16) |
                                  ((idxData[1] & 0xFF) << 8) |
                                  ((idxData[0] & 0xFF)))
                    # 读取数据，回调保存数据
                    if dataLength > 0:
                        imgData = bundle.read(dataLength)
                        if callback is not None:
                            callback(rowIndex + row, colIndex + column, imgData)

                column = column + 1
            row = row + 1
    finally:
        if bundle:
            bundle.close()


def judgeSubBundle(row, col):
    row_y = row % 128
    column_y = col % 128

    row = (row - row_y)
    col = (col - column_y)

    row = hex(row)[2:].zfill(4)
    col = hex(col)[2:].zfill(4)
    return 'R' + row + 'C' + col
def judgeSubBundle1(row, col):
    row_y = math.floor(row / 128)
    column_y = math.floor(col / 128)

    row = row_y * 128
    col = column_y * 128

    row = hex(row)[2:].zfill(4)
    col = hex(col)[2:].zfill(4)
    return 'R' + row + 'C' + col
